<?php


namespace App\Services\WeChat;


class WeChatLibService
{
    /**
     * MakeSign 生成签名
     *
     * @param  array   $data
     * @param  string  $aes_key
     * @param  string  $signType
     *
     * @return string
     */
    static public function makeSign(array $data, string $aes_key, string $signType = 'HMAC-SHA256')
    {

        //签名步骤一：按字典序排序参数
        ksort($data);

        $string = self::toUrlParams($data);
        //签名步骤二：在string后加入KEY
        $string = $string."&key=".$aes_key;

        //签名步骤三：MD5加密或者HMAC-SHA256
        if ($signType == 'md5')
        {
            //如果签名小于等于32个,则使用md5验证
            $string = md5($string);
        }
        else
        {
            //是用sha256校验
            $string = hash_hmac("sha256", $string, $aes_key);
        }
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 验证签名
     *
     * @param          $data
     * @param  string  $sign_type
     *
     * @return bool
     * @throws \Exception
     */
    static public function checkSign($data, $sign_type = '')
    {
        if (self::makeSign($data, $sign_type ? $sign_type : '') === $data['sign'])
        {
            return true;
        }
        throw new \Exception('Sign error');
    }

    /**
     * ToUrlParams     格式化参数格式化成url参数
     *
     * @param $data
     *
     * @return string
     */
    static private function toUrlParams(array $data)
    {
        $buff = "";
        foreach ($data as $k => $v)
        {
            if ($k != "sign" && $v !== "" && !is_array($v))
            {
                $buff .= $k."=".$v."&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 输出xml字符
     *
     * @param $data
     *
     * @return string
     * @throws \Exception
     */
    static public function toXml($data)
    {
        if (!is_array($data) || count($data) <= 0)
        {
            throw new \Exception('Params cannot be null');
        }

        $xml = "<xml>";
        foreach ($data as $key => $val)
        {
            if (is_numeric($val))
            {
                $xml .= "<".$key.">".$val."</".$key.">";
            }
            else
            {
                $xml .= "<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     *
     * @param  string  $xml
     *
     * @return mixed
     *
     * @throws \Exception
     */
    static public function fromXml($xml)
    {
        if (!$xml)
        {
            throw new \Exception('Params cannot be null');
        }

        libxml_disable_entity_loader(true);

        $xml_parser = xml_parser_create();

        if (!xml_parse($xml_parser, $xml, true))
        {
            xml_parser_free($xml_parser);
            throw new \Exception('Xml parse error');
        }
        else
        {
            $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        }

        return $arr;
    }

    /**
     * getRandChar 获取随机字符串
     *
     * @param  int  $length
     *
     * @return null|string
     */
    static public function getRandChar($length = 32)
    {
        $str    = null;
        $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz";
        $newStr = str_shuffle($strPol);
        $max    = strlen($strPol) - 1;
        for ($i = 0; $i < $length; $i++)
        {
            $str .= $newStr[mt_rand(0, $max)];
        }
        return $str;
    }

    /**
     * HTTPS 请求
     *
     * @param  string  $url
     * @param  string  $data
     * @param  array   $headers
     * @param  string  $sslCertPath
     * @param  string  $sslKeyPath
     *
     * @return array
     * @throws \Exception
     */
    static public function httpsRequest(string $url, $data = '', array $headers = [], string $sslCertPath = '', string $sslKeyPath = '')
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);

        if (!empty($data))
        {
            curl_setopt($curl, CURLOPT_POST, 1);
            curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
        }

        curl_setopt($curl, CURLOPT_TIMEOUT, 30);

        if ($sslCertPath && $sslKeyPath)
        {
            if (!file_exists($sslCertPath) || !file_exists($sslKeyPath))
            {
                throw new \Exception('Certificate does not exist');
            }
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
            curl_setopt($curl, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($curl, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($curl, CURLOPT_SSLKEY, $sslKeyPath);
        }
        else
        {
            if (substr($url, 0, 5) == 'https')
            {
                curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
                curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
            }
        }

        if (!empty($headers))
        {
            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
        }
        curl_setopt($curl, CURLOPT_HEADER, true);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        $output          = curl_exec($curl);
        $header_size     = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
        $response_header = substr($output, 0, $header_size);
        $http_code       = curl_getinfo($curl, CURLINFO_HTTP_CODE);
        $response_body   = substr($output, $header_size);
        $error           = curl_error($curl);
        curl_close($curl);

        return [$response_body, $http_code, $response_header, $error];
    }

    /**
     * 处理返回
     *
     * @param  array  $res  网关返回数据
     *
     * @return mixed
     * @throws \Exception
     */
    static public function disposeReturn(array $res)
    {
        if ($res[1] === 200)
        {
            return $data = self::fromXml($res[0]);
        }

        LogService::error('Wechat gateway error: '.print_r($res, true));

        throw new \Exception('Wechat gateway error.');
    }

    /**
     * 敏感信息加密
     *
     * @param  string  $string
     * @param  string  $public_key_path  平台证书路径
     *
     * @return string
     */
    static public function publicKeyEncrypt(string $string, string $public_key_path)
    {
        $public_key = file_get_contents($public_key_path);
        $encrypted  = '';
        openssl_public_encrypt($string, $encrypted, $public_key);
        $sign = base64_encode($encrypted);

        return $sign;
    }

    /**
     * 解密证书
     *
     * @param  array   $data  证书数据
     * @param  string  $aes_key
     *
     * @return bool
     * @throws \Exception
     */
    static public function decodePem(array $data, string $aes_key)
    {
        if (!isset($data['encrypt_certificate']) || empty($data['encrypt_certificate']))
        {
            throw new \Exception('params error.');
        }

        $encryptCertificate = $data['encrypt_certificate'];
        $ciphertext         = base64_decode($encryptCertificate['ciphertext']);
        $associated_data    = $encryptCertificate['associated_data'];
        $nonce              = $encryptCertificate['nonce'];
        // sodium_crypto_aead_aes256gcm_decrypt >=7.2版本，去php.ini里面开启下libsodium扩展就可以，之前版本需要安装libsodium扩展，具体查看php.net（ps.使用这个函数对扩展的版本也有要求哦，扩展版本 >=1.08）
        return sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associated_data, $nonce, $aes_key);
    }
}
